fix(extensions): make mandatory-hook dispatch contract self-contained#2901
fix(extensions): make mandatory-hook dispatch contract self-contained#2901Quratulain-bilal wants to merge 1 commit into
Conversation
mnriem
left a comment
There was a problem hiding this comment.
Thanks for the contribution. A couple of questions before we can move forward:
-
Orchestrator claim: The PR body references
specify runand "CLI orchestrators that watch agent output" as existing consumers of theEXECUTE_COMMAND:directive. Can you point to the code path where this dispatch actually happens today? If no orchestrator currently consumes the directive, the backward-compatibility framing is misleading and the PR description should be corrected. -
AI disclosure: Was this PR authored with AI assistance? If so, please disclose per contributing guidelines.
The underlying fix (agent self-dispatches mandatory hooks in agent-direct sessions) addresses a real problem, but the bar is high for sweeping template changes and we need confidence the framing accurately reflects the codebase.
|
closed this by accident while editing the description - reopened. on the orchestrator question: you're right, I rechecked and couldn't find anything in the repo that actually consumes and yes, this was built with AI assistance (Claude Code) - code, tests and description, reviewed by me. added the if the template changes are too broad I can cut it down to just the format_hook_message() change + tests and do the |
There was a problem hiding this comment.
Pull request overview
This PR strengthens the extension mandatory-hook “dispatch contract” so agent-direct invocations (Claude Code / Cursor / Codex, etc.) don’t silently emit EXECUTE_COMMAND: without actually running the hook, by explicitly instructing the agent to self-dispatch and wait.
Changes:
- Updates runtime hook messaging to explain that
EXECUTE_COMMAND:is a signal (not a dispatcher) and that mandatory hooks must be invoked and awaited. - Updates all bundled command templates to embed the same dispatch-contract guidance and tightens “Done When” criteria.
- Adds/extends tests to prevent regressions in both runtime messages and bundled templates.
Show a summary per file
| File | Description |
|---|---|
src/specify_cli/extensions.py |
Extends mandatory-hook message text to instruct self-dispatch. |
templates/commands/analyze.md |
Adds dispatch-contract bullet after mandatory hook blocks. |
templates/commands/checklist.md |
Adds dispatch-contract bullet after mandatory hook blocks. |
templates/commands/clarify.md |
Adds dispatch-contract bullet after mandatory hook blocks and tightens Done When. |
templates/commands/constitution.md |
Adds dispatch-contract bullet after mandatory hook blocks. |
templates/commands/implement.md |
Adds dispatch-contract bullet after mandatory hook blocks and tightens Done When. |
templates/commands/plan.md |
Adds dispatch-contract bullet after mandatory hook blocks and tightens Done When. |
templates/commands/specify.md |
Adds dispatch-contract bullet after mandatory hook blocks and tightens Done When. |
templates/commands/tasks.md |
Adds dispatch-contract bullet after mandatory hook blocks and tightens Done When. |
templates/commands/taskstoissues.md |
Adds dispatch-contract bullet after mandatory hook blocks. |
extensions/EXTENSION-API-REFERENCE.md |
Documents the dispatch contract for extension authors. |
tests/test_extensions.py |
Adds tests for the runtime message contract and template coverage. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 12/12 changed files
- Comments generated: 19
|
Please address Copilot feedback |
|
Yes, the template changes are way too broad. We should look into a fix with a smaller surface |
|
makes sense, agree it's too broad. while trying to shrink it I found the reason it spread: format_hook_message() in extensions.py isn't actually reached at runtime its only caller is check_hooks_for_event(), which nothing in the CLI or agent path calls. the hook block is emitted by the agent following the command-template instructions (it reads .specify/extensions.yml and outputs the block itself), so the templates are where the behavior actually lives hence all 9. two smaller options: any preference on direction? |
|
Pick one and lets see how it looks |
… directive In agent-direct invocations (a slash command run inside Claude Code, Cursor, etc.) nothing watches agent output for EXECUTE_COMMAND:, so a mandatory hook that is only emitted never runs and the failure is silent (github#2730). Add one line after each mandatory-hook block in the command templates instructing the agent to actually invoke the hook and wait for it before continuing. Minimal version of the earlier broad change, per maintainer feedback: example blocks are left untouched; only a single instruction line is added per mandatory-hook block. Fixes github#2730
5339123 to
aabc7c7
Compare
|
went with option 1 - one instruction line after each mandatory-hook block, example blocks untouched. rebased on current main so the diff is clean: 9 files, 18 lines. |
Fixes #2730
Problem
The
EXECUTE_COMMAND:directive emitted foroptional: falsehooks is meant as a dispatch signal. In agent-directinvocations (a slash command run inside Claude Code, Cursor, Codex, etc. — the default pattern for extension users),
nothing watches for it. The agent emits the directive, waits, and moves on — the hook never runs, and the failure is
completely silent.
This is the execution half of #2688: PR #2713 (v0.8.15) fixed the emission half (the directive now reliably appears in
agent output), but nothing dispatches it.
This implements Resolution Path 1 from the issue — the lowest-cost fix: make the contract self-contained so the agent
performs the dispatch itself.
Changes
format_hook_message()(src/specify_cli/extensions.py): after theEXECUTE_COMMAND:/EXECUTE_COMMAND_INVOCATION:lines, the message now tells the agent the directive alone executes nothing, and it mustinvoke the rendered hook command itself and wait for completion before continuing.
templates/commands/*.md(all 9 command templates): added the same dispatch-contract bullet after everymandatory-hook block (pre- and post-execution, 18 sites), and tightened the
Done Whenchecklist — emittingEXECUTE_COMMAND:alone does not count as dispatched.extensions/EXTENSION-API-REFERENCE.md: documented the dispatch contract so extension authors don't assume anexternal dispatcher will pick up the directive.
Backward compatibility
The
EXECUTE_COMMAND:/EXECUTE_COMMAND_INVOCATION:directive lines are unchanged and still emitted. To be precise: Icould not find a code path in this repository that currently consumes the directive —
HookExecutor.execute_hook()exists but has no production callers, and the
auto_execute_hookssetting is written as a default but never read fordispatch. The directive is preserved for any external or future consumer, but nothing in-repo depends on it today, which
is consistent with the failure mode reported in #2730. Optional hooks are untouched: they remain a user-facing prompt
with no self-dispatch mandate.
Tests
test_mandatory_hook_message_instructs_agent_to_dispatch— the runtime hook message contains the self-dispatchinstruction with the rendered invocation.
test_optional_hook_message_has_no_dispatch_mandate— optional hooks stay advisory; no behavioral change.test_bundled_templates_state_directive_dispatch_contract— everyEXECUTE_COMMANDblock in every bundled templatecarries the contract bullet, so a future template edit can't silently regress agent-direct invocations back to
emit-and-stall.
Full
tests/test_extensions.pysuite passes (248 passed). The two pre-existingtest_setup_tasks.pyPowerShell-hintfailures reproduce on clean
mainand are unrelated to this change.AI disclosure
This PR was authored with AI assistance (Claude Code) code changes, tests, and description under my direction and review.